package com.cit.draw.redis;

import java.util.Collection;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import redis.clients.jedis.BinaryJedis;

import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.runtime.RuntimeSchema;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.name.Named;

/**
 * @author gutzeit
 * @since 6/3/12 9:41 AM
 */
public class RedisCachingProvider implements CachingProvider {
	private static final Logger log = LoggerFactory
			.getLogger(RedisCachingProvider.class);

	@Inject
	private BinaryJedisPool jedisPool;

	@Override
	@Nullable
	public <T> T getEntity(@Nonnull Class<T> entityType,
			@Nonnull String entityIdentifier) {
		Preconditions.checkNotNull(jedisPool);

		byte[] key = makeBinaryKey(entityType.getSimpleName(), entityIdentifier);
		BinaryJedis jedis = null;
		byte[] result = null;
		try {
			jedis = jedisPool.getResource();
			synchronized (key) {
				result = jedis.get(key);
			}

			if (result == null) {
				jedisPool.returnResource(jedis);
				return null;
			} else {
				try {
					Schema<T> schema = RuntimeSchema.getSchema(entityType);
					T loadedObject = entityType.newInstance();
					ProtostuffIOUtil.mergeFrom(result, loadedObject, schema);
					return loadedObject;
				} catch (InstantiationException e) {
					log.error("An error has occurred", e);
				} catch (IllegalAccessException e) {
					log.error("An error has occurred", e);
				}
			}

		} finally {
			if (jedis != null) {
				jedisPool.returnResource(jedis);
			}
		}

		return null;
	}

	@Override
	public <T> void putEntity(@Nonnull T entity,
			@Nonnull String entityIdentifier) {
		// we should check if it already existed

		byte[] key = makeBinaryKey(entity.getClass().getSimpleName(),
				entityIdentifier);

		// Serialize
		@SuppressWarnings("rawtypes")
		Schema schema = RuntimeSchema.getSchema(entity.getClass());
		LinkedBuffer buffer = LinkedBuffer
				.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
		@SuppressWarnings("unchecked")
		byte[] serialized = ProtostuffIOUtil
				.toByteArray(entity, schema, buffer);

		BinaryJedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			synchronized (key) {
				if (jedis.exists(key)) {
					jedis.del(key);
				}
				jedis.setex(key, 60 * 60 * 24, serialized);
			}

		} finally {
			if (jedis != null) {
				jedisPool.returnResource(jedis);
			}

		}

	}

	private byte[] makeBinaryKey(String entityType, String entityIdentifier) {
		return (entityType + "_" + entityIdentifier).getBytes();
	}

	@Override
	public <T> void deleteEntityCache(Class<T> entityType,
			String entityIdentifier) {

		byte[] key = makeBinaryKey(entityType.getSimpleName(), entityIdentifier);
		BinaryJedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.del(key);
		} finally {
			if (jedis != null) {
				jedisPool.returnResource(jedis);
			}

		}

	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> Collection<T> getQueryResult(Class<T> entityType,
			Object... parameters) {
		return null;
	}

	@Override
	public <T> void putQueryResult(Collection<T> results, Class<T> entityType,
			Object... parameters) {

	}

	/**
	 * Make the result key for caching Query results.
	 * 
	 * Key format: queryName_param1_param2....
	 * 
	 * @param queryName
	 *            queryName is the simple name for the query class
	 * 
	 * @param parameters
	 *            the filter field and value when make query to Mongo. Must be
	 *            String. A key should use the value as the param.
	 * 
	 * @return resultKey: the key saved in the cache.
	 * 
	 */
	private String makeResultsKey(String queryName, Object... parameters) {
		StringBuilder builder = new StringBuilder();
		builder.append(queryName);
		for (Object param : parameters) {
			builder.append("_");
			builder.append(param.toString());
		}
		String identifer = new String(builder);
		return identifer;
	}

	 @Override
	 public <T> void invalidateEntityQueryCache(Class<T> entityType) {
	 Preconditions.checkNotNull(entityType);
	
	 String simpleName = entityType.getSimpleName();
	 BinaryJedis jedis = null;
	
	 try {
	 jedis = jedisPool.getResource();
	 Set<byte[]> keys = jedis.keys(("^"+simpleName).getBytes());
	 if(keys == null ){
	 return;
	 }
	 for(byte[] key : keys){
	 jedis.del(key);
	 }
	
	 } finally {
	 if (jedis != null) {
	 jedisPool.returnResource(jedis);
	 }
	 }
	
	 }

}
